package model;

import java.io.File;
import java.io.IOException;
import java.security.acl.LastOwnerException;
import java.util.Iterator;
import java.util.Map;

import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;

import org.tritonus.share.sampled.TAudioFormat;
import org.tritonus.share.sampled.file.TAudioFileFormat;

import persistence.Playlist;
import persistence.Track;

/**
 * 
 * Der Player übernimmt die ganze Logik. Er kann lieder <br>
 * abspielen. Durch die Wiedergabeiste Navigieren. Lieder Anhalten<br>
 * und Stoppen. Nach dem Anhalten kann er weiterspielen.<br>
 * 
 * TODO Eigenschaften auslesen
 * TODO Equilzier Werte Steuern
 * TODO Titellänge berechnen
 * TODO aktuelle Position Berechnen nicht nur in Bytes angeben
 *
 * @author Patrick
 * @sinde 30.11.2011
 *
 */
public class Player {
	private Track actual;
	private SourceDataLine line;
	private AudioInputStream din;
	private Integer nBytesWritten;
	private Playlist playlist;
	private PlayingThread playThread;
	private boolean isPlaying = false;
	
	/**
	 * Erzeugt einen Player mithilfe eines File Objektes.
	 * 
	 * @author Patrick
	 * @since 30.11.2011
	 * @param file
	 * @throws IOException
	 * @throws LineUnavailableException
	 * @throws UnsupportedAudioFileException
	 */
	public Player(File file) throws IOException, LineUnavailableException,
			UnsupportedAudioFileException {
		this.insertTrack(new Track(file));
		this.playlist = new Playlist(this.actual, "default");
		this.nBytesWritten = new Integer(0);
	}

	/**
	 * Player erzeugen mit Wiedergabeliste.
	 * 
	 * @author Patrick
	 * @since 30.11.2011
	 * @param p
	 * @throws LineUnavailableException
	 */
	public Player(Playlist p) throws LineUnavailableException {
		this.playlist = p;
		this.nBytesWritten = new Integer(0);
		this.insertTrack(this.playlist.getFirst());
	}

	/**
	 * Einlegen von Titeln. Ist elementar wichtig weil er hier die Aktuellen<br>
	 * Decoderinformationen zusammenbaut um einen Decodierten Stream  zu erzuegen.
	 * Zudem wird ein Ausgabestream erzeugt in dem nachher reingeschrieben wird.
	 *
	 * @author Patrick
	 * @since 30.11.2011
	 *
	 * @param track
	 * @throws LineUnavailableException
	 */
	public void insertTrack(Track track) throws LineUnavailableException {
		this.line = null;
		this.din = null;
		this.actual = track;
		this.din = AudioSystem.getAudioInputStream(
				this.actual.getDecodedFormat(),
				this.actual.getTrackInputStream());
		this.line = this.actual.getLine(this.actual.getDecodedFormat());
		if(this.playlist == null){
		    System.out.println("ERROR");
		}
	}

	/**
	 * beenden der Wiedergabe und alle offenen Verbindungen schließen.
	 * Also auch den Thread anhalten und beenden.
	 *
	 * @author Patrick
	 * @since 30.11.2011
	 *
	 * @throws IOException
	 */
	public void stop() throws IOException {
		if (this.isPlaying) {
			line.stop();

			this.playThread.interrupt();
			this.playThread.stopped();
			this.playThread = null;

			line.close();
			din.close();
			nBytesWritten = new Integer(0);
			this.isPlaying = false;
		}
	}

	/**
	 * Abspielen des aktuell eingelegten Tracks. 
	 * Track wird eingelegt um einen neuen Stream zu bekommen.
	 * Ein neuer Thread wird gestartet und es kann maximal einer laufen.
	 *
	 * @author Patrick
	 * @since 30.11.2011
	 *
	 * @throws IOException
	 * @throws LineUnavailableException
	 * @throws UnsupportedAudioFileException
	 */
	public void play() throws IOException, LineUnavailableException,
			UnsupportedAudioFileException {
		if (!this.isPlaying && (line != null || din != null)) {
			this.insertTrack(new Track(this.actual));

			this.playThread = new PlayingThread(line, din, nBytesWritten);
			this.playThread.setPriority(Thread.MAX_PRIORITY);
			this.isPlaying = true;
			this.playThread.start();

		} else {
			System.out.println("keine Audioinformationen gefunden");
		}
	}

	/**
	 * Pausieren der Wiedergabe.
	 *
	 * @author Patrick
	 * @since 30.11.2011
	 *
	 * @throws InterruptedException
	 */
	public void pause() throws InterruptedException {
		if (this.isPlaying){
			this.nBytesWritten = this.playThread.stopped();
			this.isPlaying = false;
		}
	}

	/**
	 * Gibt die Länge des Tracks zurück in Minuten.
	 *
	 * @author Patrick
	 * @throws IOException 
	 * @throws UnsupportedAudioFileException 
	 * @since 30.11.2011
	 *
	 */
	public static String getTrackLength(File file) throws UnsupportedAudioFileException, IOException {
        double seconds = Player.getFileSeconds(file);
        String minutes = "";
        minutes += (int)(seconds / 60);
        minutes += (seconds % 60);
        if(minutes.contains("."))
            minutes = minutes.substring(0, minutes.lastIndexOf('.'));
        
	    return minutes;
	}

	/**
     *
     * @author Patrick
     * @since 15.01.2012
     *
     * @param file
     * @return
	 * @throws IOException 
	 * @throws UnsupportedAudioFileException 
     */
    public static double getFileSeconds(File file) throws UnsupportedAudioFileException, IOException {
        AudioFileFormat baseFileFormat = null;
        AudioFormat baseFormat = null;
        double bytes = 0;
        double val = 0;
        baseFileFormat = AudioSystem.getAudioFileFormat(file);
        baseFormat = baseFileFormat.getFormat();
        // TAudioFileFormat properties
        if (baseFileFormat instanceof TAudioFileFormat) {
            Map<String, ?> properties = ((TAudioFileFormat) baseFileFormat)
                    .properties();
            bytes = (Integer) properties.get("mp3.length.bytes");
        }
        if (baseFormat instanceof TAudioFormat) {
            Map<String, ?> properties = ((TAudioFormat) baseFormat)
                    .properties();
            val = (Integer) properties.get("bitrate");
        }
        return bytes / (val / 8);
    }

    /**
	 * Springt in der Wiedergabe zu dem Nächsten Track 
	 * und gibt diesen sofort aus.
	 *
	 * @author Patrick
	 * @since 30.11.2011
	 *
	 * @throws LineUnavailableException
	 * @throws UnsupportedAudioFileException
	 * @throws IOException
	 */
	public void next() throws LineUnavailableException,
			UnsupportedAudioFileException, IOException {
		if (this.isPlaying)
			this.stop();
		int index;
		index = this.playlist.indexOf(this.actual);
		Iterator<Track> it = this.playlist.listIterator(index + 1);
		if (it.hasNext())
			this.insertTrack(new Track(it.next()));
		else
			this.insertTrack(new Track(this.playlist.getFirst()));
		this.play();
	}

	/**
	 * Springt in der wiedergabeliste genau einen Track<br>
	 *  zurück und gibt diesen sofort aus.
	 *
	 * @author Patrick
	 * @since 30.11.2011
	 *
	 * @throws LineUnavailableException
	 * @throws UnsupportedAudioFileException
	 * @throws IOException
	 */
	public void back() throws LineUnavailableException,
			UnsupportedAudioFileException, IOException {
		if (this.isPlaying)
			this.stop();
		int index;
		index = this.playlist.indexOf(this.actual);
		Object[] objs = this.playlist.toArray();
		if (index > 0)
			this.insertTrack(new Track((Track) objs[index - 1]));
		else
			this.insertTrack(new Track((Track) objs[objs.length - 1]));
		this.play();
	}

    /**
     * This method return the variable playlist.
     * @return the playlist
     */
    public Playlist getPlaylist() {
        return playlist;
    }

    /**
     * This methods sets the variable playlist.
     * @param playlist the playlist to set
     */
    public void setPlaylist(Playlist playlist) {
        this.playlist = playlist;
    }

    /**
     * This method return the variable actual.
     * @return the actual
     */
    public Track getActual() {
        return actual;
    }

    /**
     * This methods sets the variable actual.
     * @param actual the actual to set
     */
    public void setActual(Track actual) {
        this.actual = actual;
    }
	
    
	
}
